// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: chunhui

#pragma once
#include <cstdint>
#include <vector>

#include <fmt/format.h>
#include <glog/logging.h>

#include "cc/appcoin2/support/feed/interval_feed_tool/ohlc.h"
#include "cc/appcoin2/support/feed/interval_feed_tool/spread.h"
#include "cc/appcoin2/support/feed/interval_feed_tool/std.h"
#include "cc/appcoin2/support/feed/interval_feed_tool/vwap.h"
#include "coin2/exchange/base/symbology/product_info.h"
#include "coin2/exchange/feed.pb.h"
#include "coin2/exchange/interval_feed_enum.pb.h"

namespace interval_feed {

using coin2::exchange::base::symbology::IProductInfo;
using namespace coin2::exchange::interval_feed_enum;

class FeedSummaryCalculator {
 public:
  FeedSummaryCalculator() {
    ohlc_.resize(PriceType_ARRAYSIZE);
    t_ohlc_.resize(TsType_ARRAYSIZE);
    vwap_.resize(VwapType_ARRAYSIZE);
    ma_.resize(MarketAnalysisType_ARRAYSIZE);
    std::fill(ma_.begin(), ma_.end(), std::numeric_limits<double>::quiet_NaN());
    lsr_.resize(LongShortRatioType_ARRAYSIZE);
    std::fill(lsr_.begin(), lsr_.end(), std::numeric_limits<double>::quiet_NaN());
  }

  void UpdateBook(int64_t ts, double bid, double ask) {
    ohlc_[PriceType::BID0].Update(bid);
    ohlc_[PriceType::ASK0].Update(ask);
    double mid = 0.5 * (bid + ask);
    ohlc_[PriceType::MID].Update(mid);
    std_.Update(mid);
    double spread = (ask - bid) / mid;
    tws_.Update(ts, spread);
  }

  void UpdateTrade(double price, double qty, coin2::exchange::feed::TradeSide side) {
    // Ignore all trade side other than buy/sell
    if (side == coin2::exchange::feed::TradeSide::TRADE_BUY_SIDE) {
      vwap_[VwapType::VWAP_BUY].Update(price, qty);
      ohlc_[PriceType::TRADE].Update(price);
      vwap_[VwapType::VWAP].Update(price, qty);
    } else if (side == coin2::exchange::feed::TradeSide::TRADE_SELL_SIDE) {
      vwap_[VwapType::VWAP_SELL].Update(price, qty);
      ohlc_[PriceType::TRADE].Update(price);
      vwap_[VwapType::VWAP].Update(price, qty);
    }
  }

  void UpdateLiquidation(double price, double qty, double sign) {
    if (sign > 0) {
      vwap_[VwapType::VWAP_LIQUIDATION_BUY].Update(price, qty);
    } else if (sign < 0) {
      vwap_[VwapType::VWAP_LIQUIDATION_SELL].Update(price, qty);
    }
  }

  void UpdateGeneralOhlc(PriceType price_type, double value) { ohlc_[price_type].Update(value); }

  void UpdateTimestamp(TsType ts_type, int64_t ts) {
    // TODO(chunhui): try to make ohlc generic
    auto value = static_cast<double>(ts);
    t_ohlc_[ts_type].Update(value);
  }

  void UpdateMarketAnalysis(double total_supply,
                            double max_supply,
                            double circulating_supply,
                            double market_cap_in_usd,
                            double tvl_in_usd) {
    ma_[MarketAnalysisType::TOTAL_SUPPLY] = total_supply;
    ma_[MarketAnalysisType::MAX_SUPPLY] = max_supply;
    ma_[MarketAnalysisType::CIRCULATING_SUPPLY] = circulating_supply;
    ma_[MarketAnalysisType::MARKET_CAP_IN_USD] = market_cap_in_usd;
    ma_[MarketAnalysisType::TVL_IN_USD] = tvl_in_usd;
  }

  void UpdateLongShortRatio(double long_short_ratio, LongShortRatioType type){
    lsr_[type] = long_short_ratio;
  }

  double ReadOhlc(PriceType price_type, OhlcType ohlc_type) const {
    return ohlc_[price_type].Get(ohlc_type);
  }

  double ReadOhlc(TsType ts_type, OhlcType ohlc_type) const {
    return t_ohlc_[ts_type].Get(ohlc_type);
  }

  double ReadVwap(VwapType vwap_type) const { return vwap_[vwap_type].GetVwap(); }

  double ReadVolume(VolumeType volume_type) const { return vwap_[volume_type].GetQSum(); }

  double ReadDeribitOptionDollarVolume(VolumeType volume_type) const { return vwap_[volume_type].GetPqSum(); }

  double ReadDollarVolume(VolumeType volume_type, const IProductInfo& pi) const {
    double contract_value = 1.0;
    if (pi.contract_value() > 0) {
      contract_value = pi.contract_value();
    }
    if (pi.is_inverse()) {
      return vwap_[volume_type].GetQSum() * contract_value;
    } else {
      return vwap_[volume_type].GetPqSum() * contract_value;
    }
  }

  double ReadStd() const { return std_.GetStd(); }

  double ReadTimeWeightedSpread(int64_t end_ts) const { return tws_.GetSpread(end_ts); }

  double ReadMarketAnalysis(MarketAnalysisType ma_type) const { return ma_[ma_type]; }

  double ReadLongShortRatio(LongShortRatioType lsr_type) const { return lsr_[lsr_type]; }

  std::vector<OhlcCalculator> ohlc_;  // OHLC of price
  std::vector<OhlcCalculator> t_ohlc_;  // OHLC of timestamp
  std::vector<VwapCalculator> vwap_;
  std::vector<double> book_etft_diff_;
  std::vector<double> trd_etft_diff_;
  StdCalculator std_;
  TimeWeightedSpreadCalculator tws_;
  std::vector<double> ma_;
  std::vector<double> lsr_;
};

}  // namespace interval_feed
